package org.hackystat.devcathlon.engine.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;
import org.hackystat.devcathlon.engine.match.Match;
import org.hackystat.devcathlon.engine.team.Team;

/**
 * Awards points for every passing build and deducts points for every failing build.
 * <p>
 * Requires the following properties in the configuration.xml file for this event:
 * <ul>
 * <li>WakeupInterval: How frequently this event is invoked, and how far back it looks for data.
 * <li>SuccessWeight: The integer given for one successful build.
 * <li>FailedWeight: The integer given for one failed build.
 * <li>BonusWeight: The integer given for the bonus part of this event.
 * <li>BonusRange: An integer that is the range for the bonus part on how far back it looks for
 * data.
 * </ul>
 * <p>
 * Specification:
 * <ul>
 * <li>Checks all Build sensor data since the last wakeup interval for all build sensor data
 * instance. Records the first timestamp associated with that sensor data.
 * <li>Retrieves all build sensor data. Finds out if there are any successful and failed builds.
 * <li>Base Score: 0 if no previous build data exists. Otherwise base score is between -1 and +1
 * depending upon each successful build or failed build since last build measurement.
 * <li>Bonus Score: Award Bonus Score if this shows at least one successful build and no failures in
 * the past Bouns Range of measurement.
 * </ul>
 * 
 * @author Ka Hung Phillip Lau
 * 
 */
public class PassTheBuild extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;

  /** Name of the event. */
  public static final String eventName = "Pass The Build";

  /** Description of the event. */
  public static final String description = "Give points for passing builds. ";

  /** The actual success build point weight. */
  private int successWeight;
  /** The property key for successWeight. */
  public static final String successWeightKey = "SuccessWeight";

  /** The actual failed build point weight. */
  private int failedWeight;
  /** The property key for failedWeight. */
  public static final String failedWeightKey = "FailedWeight";

  /** The actual bonus weight. */
  private int bonusWeight;
  /** The property key for bonusWeight. */
  public static final String bonusWeightKey = "BonusWeight";
  /** The actual bonus range. */
  private int bonusRange;
  /** The property key for bonusRange. */
  public static final String bonusRangeKey = "BonusRange";

  /**
   * Creates a new event for PassTheBuild. Ensures all required properties are available.
   * 
   * @throws EventException If the configuration cannot be created.
   */
  public PassTheBuild() throws EventException {
    super(eventName, description);
    try {
      successWeight = Integer.parseInt(configManager.getProperty(eventName, successWeightKey));
      failedWeight = Integer.parseInt(configManager.getProperty(eventName, failedWeightKey));
      bonusWeight = Integer.parseInt(configManager.getProperty(eventName, bonusWeightKey));
      bonusRange = Integer.parseInt(configManager.getProperty(eventName, bonusRangeKey));
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
  }

  /**
   * Returns the score for the event.
   * 
   * @param score The current score for event.
   * @return score The total score for event.
   */
  @Override
  public int computePoints(EventScore score) {
    return score.getBaseScore();
  }

  /**
   * Calculates the score for the event, regular score plus bonus score.
   * 
   * @param match The name of match.
   * @param team The name of team.
   * @param timestamp The timestamp given when this event is invoke.
   * @return score The total score for the event.
   * @throws EventException If there is no build data.
   */
  @Override
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException {
    // [0] create the default score, which awards no points.
    EventScore score = new EventScore(timestamp, match, team);
    int bonusScore = 0;
    int buildScore = 0;
    int totalScore = 0;

    // [1] find the project and the sensorbase client.
    Project project = match.getProject(team);
    SensorBaseClient client = match.getSensorBaseClient(team);
    // [2] get the Build sensor data for the previous interval of time.
    String projectName = project.getName();
    String owner = project.getOwner();
    String sdt = "Build";
    XMLGregorianCalendar endTime = timestamp;
    XMLGregorianCalendar startTime = Tstamp.incrementMinutes(endTime, wakeupInterval * -1);

    SensorDataIndex index;
    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting build sensor data for match %s and team %s", match.getName(), team
            .getName()));
    try {
      index = client.getProjectSensorData(owner, projectName, startTime, endTime, sdt);
    }
    catch (Exception e) {
      throw new EventException("Error retrieving build data.", e);
    }
    // [3] Get points from builds if any.
    try {
      buildScore = getBuildScore(client, index);
      totalScore = buildScore;

      bonusScore = getBonusScore(client, owner, projectName, endTime);
      totalScore = totalScore + bonusScore;
    }
    catch (Exception e) {
      score.setBaseScore(totalScore);
      return score;
    }
    score.setBaseScore(totalScore);
    return score;
  }

  /**
   * Finds bonus part of event. No fails and at least one successful build in bonus range.
   * 
   * @param client The client name.
   * @param owner The name of owner.
   * @param projectName The name of project.
   * @param timestamp The timestamp of when the event is invoke.
   * @return score The bonus score for event.
   * @throws Exception If there is no build data.
   */
  private int getBonusScore(SensorBaseClient client, String owner, String projectName,
      XMLGregorianCalendar timestamp) throws Exception {

    int failedBuilds = 0;
    int successBuilds = 0;
    XMLGregorianCalendar endTime = timestamp;
    int days = 0;
    int zero = 0;

    for (int i = 0; i <= this.bonusRange; i++) {
      XMLGregorianCalendar startTime = Tstamp.incrementDays(endTime, -1);
      SensorDataIndex index = client.getProjectSensorData(owner, projectName, startTime, endTime);

      for (SensorDataRef ref : index.getSensorDataRef()) {
        SensorData data = client.getSensorData(ref);
        Property buildResultProperty = data.findProperty("Result");
        days++;

        if (buildResultProperty != null && buildResultProperty.getValue().equals("Failure")) {
          failedBuilds++;
        }
        else if (buildResultProperty != null && buildResultProperty.getValue().equals("Success")) {
          successBuilds++;
        }
      }
      endTime = startTime;
    }
    if (successBuilds > zero && failedBuilds == zero && days >= this.bonusRange) {
      return this.bonusWeight;
    }
    else {
      return 0;
    }
  }

  /**
   * Finds if there are any failed builds and successful builds for the last day.
   * 
   * @param client The client name.
   * @param index The name of index
   * @return score The total score for event.
   * @throws Exception If there is no build data.
   */

  private int getBuildScore(SensorBaseClient client, SensorDataIndex index) throws Exception {
    int successBuilds = 0;
    int failedBuilds = 0;
    for (SensorDataRef ref : index.getSensorDataRef()) {
      SensorData data = client.getSensorData(ref);
      Property buildResultProperty = data.findProperty("Result");

      if (buildResultProperty != null && buildResultProperty.getValue().equals("Failure")) {
        failedBuilds++;
      }
      else if (buildResultProperty != null && buildResultProperty.getValue().equals("Success")) {
        successBuilds++;
      }
    }
    return ((successBuilds * this.successWeight) + (failedBuilds * this.failedWeight));
  }

}